commonlibsse_ng\re\c/
ConsoleLog.rs

1use core::ffi::c_char;
2use core::fmt;
3use std::ffi::CString;
4
5use crate::re::{BSString::BSString, TLSData::TLSData};
6
7/// We have confirmed that any attempt to `print` beyond this size will result in a definite crash.
8const LAST_MESSAGE_BUFFER_SIZE: usize = 0x400;
9
10/// # Note
11/// The console has also confirmed that it does not support ANSI Color.
12#[repr(C)]
13pub struct ConsoleLog {
14    pub lastMessage: [c_char; LAST_MESSAGE_BUFFER_SIZE],
15    pub pad401: u8,
16    pad402: u16,
17    pad404: u32,
18    buffer: BSString,
19}
20const _: () = assert!(core::mem::size_of::<ConsoleLog>() == 0x418);
21
22impl ConsoleLog {
23    /// Returns the singleton instance of `Self`.
24    #[commonlibsse_ng_derive_internal::relocate(
25        cast_as = "*mut *mut ConsoleLog",
26        default = "None",
27        deref_once,
28        id(se = 515064, ae = 401203)
29    )]
30    #[inline]
31    pub fn get_singleton() -> Option<&'static ConsoleLog> {
32        |deref_type: DerefType| unsafe { deref_type.as_ref() }
33    }
34
35    /// Returns the mutable singleton instance of `Self`.
36    #[commonlibsse_ng_derive_internal::relocate(
37        cast_as = "*mut *mut ConsoleLog",
38        default = "None",
39        deref_once,
40        id(se = 515064, ae = 401203)
41    )]
42    #[inline]
43    pub unsafe fn get_singleton_mut() -> Option<&'static mut ConsoleLog> {
44        |deref_type: DerefType| unsafe { deref_type.as_mut() }
45    }
46
47    #[inline]
48    pub fn is_console_mode() -> bool {
49        TLSData::get_static_tls_data().is_some_and(|tls| unsafe { tls.as_ref().consoleMode })
50    }
51
52    /// Print argument c-string.
53    /// # Example
54    /// ```no_run
55    /// use commonlibsse_ng::re::ConsoleLog::ConsoleLog;
56    ///
57    /// if let Some(console) = unsafe { ConsoleLog::get_singleton_mut() } {
58    ///     console.print(c"Hello World!".as_ptr());
59    /// };
60    /// ```
61    ///
62    /// # Note
63    /// More precisely, args: va_list follows after fmt, but since Rust does not support variadic arguments, it is omitted.
64    #[commonlibsse_ng_derive_internal::relocate_fn(se_id = 50180, ae_id = 51110)]
65    #[inline]
66    pub fn print(&mut self, fmt: *const c_char) {}
67}
68
69/// Prints a formatted string to the in-game console (Skyrim).
70///
71/// This function takes a [`fmt::Arguments`] object and formats it as a string,
72/// then passes it to the native `ConsoleLog::print` function.
73///
74/// # Examples
75///
76/// ```no_run
77/// use commonlibsse_ng::re::ConsoleLog::print_fmt;
78/// print_fmt(format_args!("Health: {}", 100));
79/// ```
80///
81/// # Notes
82/// - This function allocates on the heap due to `CString::new`.
83/// - If the input string contains null bytes (`\0`), it will return early and not print anything.
84/// - This is intended for internal use by [`console_print!`] and [`console_println!`] macros.
85///
86/// # Safety
87/// Internally uses a mutable reference to the singleton `Console`, accessed via `unsafe`.
88#[inline]
89pub fn print_fmt(args: fmt::Arguments) {
90    if let Some(console) = unsafe { ConsoleLog::get_singleton_mut() } {
91        let s = format!("{}", args);
92
93        // We have confirmed that any attempt to `print` beyond this size will result in a definite crash.
94        for chunk in s.as_bytes().chunks(LAST_MESSAGE_BUFFER_SIZE) {
95            // Only try to print valid UTF-8 chunks
96            if let Ok(chunk_str) = core::str::from_utf8(chunk) {
97                if let Ok(c_str) = CString::new(chunk_str) {
98                    console.print(c_str.as_ptr());
99                }
100            }
101        }
102    }
103}